/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "profile.h"
#include "stopwatch.h"
#include <stdlib.h>
#include <string.h>

namespace Graph2D
{
	const unsigned char CPUgauge[3 * Profile::SIZE_OF_CATEGORIES] = {
		0xFF, 0x00, 0x00,	// 0:赤
		0x00, 0xFF, 0x00,	// 1:緑
		0x00, 0x00, 0xFF,	// 2:青
		0xFF, 0x00, 0xFF,	// 3:桃
		0xFF, 0xFF, 0x00,	// 4:黄
		0x00, 0xFF, 0xFF,	// 5:水
		0x00, 0x00, 0x00,	// 6:黒
		0xFF, 0xFF, 0xFF,	// 7:白
	};

	const unsigned char MEMORYgauge[3 * 3] = {
		0xFF, 0x00, 0x00,	// 赤
		0x00, 0x00, 0xFF,	// 青
		0x00, 0xFF, 0x00	// 緑
	};

	Profile& Profile::getInstance()
	{
		static Profile instance;
		return instance;
	}

	Profile::Profile() : profileIndex(0)
	{
		memset(profile, 0, sizeof(profile));
		memset(memory, 0, sizeof(memory));
		setBaseFPS(60);
		setFPS(1.f / 60.f);
	}

	Profile::~Profile()
	{
	}

	void Profile::sync()
	{
		// フレームの重さに応じてゲージのスケールを変更する
		setFPS(load());

		// スワップして次のフレームへ
		profileIndex = 1 - profileIndex;

		profile[profileIndex].startFrame = Stopwatch::now();
		for(int i = 0; i < SIZE_OF_CATEGORIES; i++)
		{
			profile[profileIndex].start[i] = profile[profileIndex].startFrame;
			profile[profileIndex].ticks[i] = 0;
		}
	}

	double Profile::load() const
	{
		const unsigned char backProfileIndex = 1 - profileIndex;

		int index;
		for(index = SYNC; index > 0; index--)
		{
			if(profile[backProfileIndex].start[index] != profile[backProfileIndex].startFrame)
				break;
		}
		const double origin = profile[backProfileIndex].startFrame;
		const double last = profile[backProfileIndex].start[index];
		const double tick = profile[backProfileIndex].ticks[index];

		return last + tick - origin;
	}

	double Profile::tick(const Profile::Category category) const
	{
		assert(category >= 0 && category < SIZE_OF_CATEGORIES);
		return profile[profileIndex].ticks[category];
	}

	void Profile::begin(const Category category)
	{
		assert(category >= 0 && category < SIZE_OF_CATEGORIES);
		profile[profileIndex].start[category] = Stopwatch::now();
	}

	void Profile::end(const Category category)
	{
		assert(category >= 0 && category < SIZE_OF_CATEGORIES);
		profile[profileIndex].ticks[category] = Stopwatch::now() - profile[profileIndex].start[category];
	}

	void Profile::setTotalSize(const size_t size)
	{
		memory[0].totalSize = size;
		memory[1].totalSize = size;
	}

	void Profile::setAllocatedSize(const size_t size)
	{
		memory[profileIndex].allocatedSize = size;
	}

	void Profile::setMaxFreeSize(const size_t size)
	{
		memory[profileIndex].maxFreeSize = size;
	}

	void Profile::setTotalFreeSize(const size_t size)
	{
		memory[profileIndex].totalFreeSize = size;
	}

	void Profile::draw()
	{
		const unsigned char backProfileIndex = profileIndex ^ 1;

		const int BOTTOM = static_cast<int>(GraphicDevice::getScreenHeight() + 0.5f) - V_MARGIN;

		////////////////////////////////////////////////////////////////////////
		// CPU
		{
			const int LEFT = H_MARGIN;
			const int RIGHT = LEFT + WIDTH;

			// ゲージの枠を描画します
			GraphicDevice::begin(GraphicDevice::TYPE_OPAQUE, GraphicDevice::TRIANGLE_STRIP, Graph2D::Color(0.2f, 0.2f, 0.2f, 1.f));
			GraphicDevice::addVertex(Vector2(static_cast<float>(LEFT  - 1), static_cast<float>(V_MARGIN - 1)));
			GraphicDevice::addVertex(Vector2(static_cast<float>(RIGHT + 1), static_cast<float>(V_MARGIN - 1)));
			GraphicDevice::addVertex(Vector2(static_cast<float>(LEFT  - 1), static_cast<float>(BOTTOM + 1)));
			GraphicDevice::addVertex(Vector2(static_cast<float>(RIGHT + 1), static_cast<float>(BOTTOM + 1)));
			GraphicDevice::end();

			// 各カテゴリ毎のゲージを描画します
			const double startFrame = profile[backProfileIndex].startFrame;
			for(int j = 0; j < SIZE_OF_CATEGORIES; j++)
			{
				const Graph2D::Color color(
					static_cast<float>(CPUgauge[3 * j + 0]) / 255.f,
					static_cast<float>(CPUgauge[3 * j + 1]) / 255.f,
					static_cast<float>(CPUgauge[3 * j + 2]) / 255.f,
					1.f
				);

				int y = static_cast<int>((profile[backProfileIndex].start[j] - startFrame) * scale + V_MARGIN);
				int h = static_cast<int>(profile[backProfileIndex].ticks[j] * scale);
				if(y > BOTTOM)
					y = BOTTOM;
				if(y + h > BOTTOM)
					h = BOTTOM - y;
				if(h > 0)
				{
					GraphicDevice::begin(GraphicDevice::TYPE_OPAQUE, GraphicDevice::TRIANGLE_STRIP, color);
					GraphicDevice::addVertex(Vector2(static_cast<float>(LEFT ), static_cast<float>(y)));
					GraphicDevice::addVertex(Vector2(static_cast<float>(RIGHT), static_cast<float>(y)));
					GraphicDevice::addVertex(Vector2(static_cast<float>(LEFT ), static_cast<float>(y + h)));
					GraphicDevice::addVertex(Vector2(static_cast<float>(RIGHT), static_cast<float>(y + h)));
					GraphicDevice::end();
				}
			}
		}

		////////////////////////////////////////////////////////////////////////
		// Memory
		{
			const float LEFT  = static_cast<float>(H_MARGIN + WIDTH + 3);
			const float RIGHT = LEFT + static_cast<float>(WIDTH);

			// ゲージの枠を描画します
			GraphicDevice::begin(GraphicDevice::TYPE_OPAQUE, GraphicDevice::TRIANGLE_STRIP, Graph2D::Color(0.2f, 0.2f, 0.2f, 1.f));
			GraphicDevice::addVertex(Vector2(LEFT  - 1.f, V_MARGIN - 1.f));
			GraphicDevice::addVertex(Vector2(RIGHT + 1.f, V_MARGIN - 1.f));
			GraphicDevice::addVertex(Vector2(LEFT  - 1.f, BOTTOM + 1.f));
			GraphicDevice::addVertex(Vector2(RIGHT + 1.f, BOTTOM + 1.f));
			GraphicDevice::end();

			if(memory[backProfileIndex].totalSize > 0)
			{
				for(int j = 0; j < 3; j++)
				{
					const Graph2D::Color color(
						static_cast<float>(MEMORYgauge[3 * j + 0]) / 255.f,
						static_cast<float>(MEMORYgauge[3 * j + 1]) / 255.f,
						static_cast<float>(MEMORYgauge[3 * j + 2]) / 255.f,
						1.f
					);

					const float scale = (GraphicDevice::getScreenHeight() - static_cast<float>(V_MARGIN * 2)) / static_cast<float>(memory[backProfileIndex].totalSize);

					float sy = V_MARGIN;
					float ey = V_MARGIN + static_cast<float>(memory[backProfileIndex].allocatedSize) * scale;

					if(j >= 1)
					{
						sy = ey;
						ey += static_cast<float>(memory[backProfileIndex].maxFreeSize) * scale;
					}
					if(j >= 2)
					{
						sy = ey;
						ey += static_cast<float>(memory[backProfileIndex].totalFreeSize - memory[backProfileIndex].maxFreeSize) * scale;
					}
					if(sy != ey)
					{
						GraphicDevice::begin(GraphicDevice::TYPE_OPAQUE, GraphicDevice::TRIANGLE_STRIP, color);
						GraphicDevice::addVertex(Vector2(LEFT , sy));
						GraphicDevice::addVertex(Vector2(RIGHT, sy));
						GraphicDevice::addVertex(Vector2(LEFT , ey));
						GraphicDevice::addVertex(Vector2(RIGHT, ey));
						GraphicDevice::end();
					}
				}
			}
		}
	}

	void Profile::setBaseFPS(const unsigned int baseFPS)
	{
		this->baseFPS = 60.0f / static_cast<float>(baseFPS);
		if(this->baseFPS < 1)
			this->baseFPS = 1;
	}

	void Profile::setFPS(const double msec)
	{
		fps = static_cast<float>(floor(msec * 60.0 + 0.1));
		if(fps < 1.0f)
			fps = 1.0f;
		if(fps < baseFPS)
			fps = baseFPS;
		scale = (60 / fps) * (GraphicDevice::getScreenHeight() - (V_MARGIN * 2)) / fps;
	}
}
